package service.impl;

import client.BaiduClient;
import common.consts.*;
import common.validation.ManualValidator;
import common.validation.ValidationResult;
import dao.*;
import domain.*;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import service.RecruitmentService;
import service.ServiceResponse;
import service.util.RowDataUtil;
import utils.MaskUtil;
import utils.Nulls;
import wrapper.*;
import wrapper.baidu.GeoCoordinate;

import java.util.*;

/**
 * Created by aceyin on 15/6/25.
 */
@Service
public class RecruitmentServiceImpl extends ServiceBase implements RecruitmentService {
    private static final Logger LOGGER = LoggerFactory.getLogger(RecruitmentServiceImpl.class);

    @Autowired
    private RecruitmentTypeDao recruitmentTypeDao;
    @Autowired
    private RecruitmentDao recruitmentDao;
    @Autowired
    private WorkLocationDao workLocationDao;
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private UserProfileDao userProfileDao;
    @Autowired
    private UserDataProfileDao dataProfileDao;
    @Autowired
    private ApplyConditionDao applyConditionDao;
    @Autowired
    private OfferDao offerDao;
    @Autowired
    private RecruitmentGroupDao recruitmentGroupDao;
    @Autowired
    private UserLocationDao locationDao;
    @Autowired
    private CompanyDao companyDao;
    @Autowired
    private BaiduClient baiduClient;

    /**
     * 保存一个招聘信息，for 商家。
     *
     * @param wrapper
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public ServiceResponse saveRecruitment(long employer, Recruitment4Employer wrapper) {
        Recruitment recruitment = wrapper.getRecruitment();
        WorkLocation workLocation = wrapper.getWorkLocation();
        ApplyCondition condition = wrapper.getApplyCondition();

        ValidationResult result = ManualValidator.create(recruitment)
                .notBlank("name")
                .notBlank("typeCode")
                .gt("salary", 0)
                .gt("owner", 0)
                .validate();
        if (result.isFailed()) {
            return ServiceResponse.fail(result.getMessage());
        }

        // 将owner设置为从 Authorization header里面获取的用户ID
        recruitment.setOwner(employer);

        Long id = recruitment.getId();
        if (id == null || id.intValue() <= 0) {
            return createNewRecruitment(recruitment, workLocation, condition);
        } else {
            return updateRecruitment(recruitment, workLocation, condition);
        }
    }

    /**
     * 根据ID获取一个招聘信息，for商家
     *
     * @param recruitmentId
     * @return
     */
    @Override
    @Transactional
    public ServiceResponse getRecruitment4Employer(long employer, long recruitmentId) {
        ServiceResponse response = ServiceResponse.success();

        Recruitment recruitment = recruitmentDao.get(recruitmentId);
        if (recruitment == null) {
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT);
        }
        long owner = recruitment.getOwner();
        if (owner != employer) {
            return ServiceResponse.fail(Consts.i18nKey.INVALID_REQUEST);
        }

        if (recruitment != null) {
            Recruitment4Employer wrapper = wrapRecruitment(recruitment);
            response.with("recruitment", wrapper);
            return response;
        }
        return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT);
    }

    /**
     * 根据状态为商家查找他自己的招聘信息。
     *
     * @param owner
     * @param status
     * @param page
     * @param rows
     * @return
     */
    @Override
    @Transactional
    public ServiceResponse find4EmployerByStatus(long owner, String status, int page, int rows) {
        List<Recruitment> recruitmentList = recruitmentDao.findByStatus(owner, status, page, rows);

        List<Recruitment4Employer> wrappers = new ArrayList<>();
        for (Recruitment recruitment : recruitmentList) {
            Recruitment4Employer wrapper = wrapRecruitment(recruitment);
            wrappers.add(wrapper);
        }
        return ServiceResponse.success().with("recruitmentList", wrappers);
    }

    /**
     * 为用户加载一个招聘信息的内容。
     * <p>
     * 如果用户已经应聘了这个工作，则可以看见其他已经应聘了这个工作的人，否则看不到
     *
     * @param recruitmentId
     * @return
     */
    @Override
    public ServiceResponse getRecruitment4User(long userid, long recruitmentId) {
        UserLocation userLocation = locationDao.get(userid);
        double latitude = userLocation.getLatitude();
        double longitude = userLocation.getLongitude();
        Map<String, Object> result = recruitmentDao.getWithDistance(recruitmentId, longitude, latitude);
        if (result == null) {
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT);
        }

        DetailRecruitment4Worker recruitment = wrapDetail(userid, recruitmentId, result);
        if (recruitment == null) {
            return ServiceResponse.fail(Consts.i18nKey.SYSTEM_ERROR);
        }
        return ServiceResponse.success().with("recruitment", recruitment);
    }

    private DetailRecruitment4Worker wrapDetail(long userid, long recruitmentId, Map<String, Object> result) {

        Map<String, Object> map = RowDataUtil.toCamelCase(result);
        try {
            DetailRecruitment4Worker recruitment = new DetailRecruitment4Worker();
            ApplyCondition condition = new ApplyCondition();
            BeanUtils.populate(condition, map);

            WorkLocationWithDistance workLocation = new WorkLocationWithDistance();
            BeanUtils.populate(workLocation, map);

            Recruitment rec = new Recruitment();
            BeanUtils.populate(rec, map);

            recruitment.setRecruitment(rec);
            recruitment.setApplyCondition(condition);
            recruitment.setWorkLocation(workLocation);

            //获取雇主信息
            long employerId = recruitment.getRecruitment().getOwner();
            UserProfile employerProfile = userProfileDao.get(employerId);
            Company employerCompany = companyDao.findByOwner(employerId);

            Employer employer = new Employer();
            employer.setCity(employerCompany.getCity());
            employer.setCompanyIntro(employerCompany.getDescription());
            employer.setCompanyName(employerCompany.getName());
            employer.setDistrict(employerCompany.getDistrict());
            employer.setProvince(employerCompany.getProvince());

            // get workmates
            Offer offer = offerDao.findOffer(recruitmentId, userid);
            // 如果当前用户应聘了他查看的工作，则可以看到他的 同事
            if (offer != null) {
                employer.setName(employerProfile.getName());
                employer.setMobile(employerProfile.getPhone());
                List<Map<String, Object>> list = offerDao.findWorkmates(recruitmentId);
                if (list == null) {
                    recruitment.setWorkmates(new ArrayList<>());
                } else {
                    List<Workmate> workmates = wrapWorkmates(list);
                    recruitment.setWorkmates(workmates);
                }
            }
            // 否则看不到同事以及商家的名字,电话
            else {
                employer.setName(MaskUtil.mask(employerProfile.getName(), 2, 3));
                employer.setMobile(MaskUtil.mask(employerProfile.getPhone(), 4, 8));
                recruitment.setWorkmates(new ArrayList<>());
            }

            recruitment.setEmployer(employer);

            return recruitment;
        } catch (Exception e) {
            LOGGER.error("Error while get recruitment", e);
            return null;
        }
    }

    private List<Workmate> wrapWorkmates(List<Map<String, Object>> list) {
        List<Workmate> result = new ArrayList<>();
        try {
            for (Map<String, Object> map : list) {
                Map<String, Object> camelCase = RowDataUtil.toCamelCase(map);
                Workmate workmate = new Workmate();
                BeanUtils.populate(workmate, camelCase);
                result.add(workmate);
            }
        } catch (Exception e) {
            LOGGER.error("Error while converter map to Workmate", e);
        }
        return result;
    }

    @Override
    public ServiceResponse find4UserByTypeOrDistrict(long userid, String city, String type, String district, int page) {
        Map<String, Object> condition = newMapWithBasicCondition();
        if (StringUtils.isNotBlank(city)) {
            condition.put("city", city);
        }
        if (StringUtils.isNotBlank(district)) {
            condition.put("district", district);
        }
        if (StringUtils.isNotBlank(type)) {
            condition.put("type_code", type);
        }
        List<SimpleRecruitment4Worker> recruitments = find4UserByConditionBasedOnUserLocation(userid, condition, page);

        return ServiceResponse.success().with("recruitments", recruitments);
    }

    /**
     * 根据group为用户列出所有的招聘信息
     *
     * @param groupCode
     * @param page
     * @return
     */
    @Override
    @Transactional
    public ServiceResponse find4UserByGroup(long userid, String city, String groupCode, int page) {
        RecruitmentGroup group = recruitmentGroupDao.findByCode(groupCode);
        if (group == null) {
            return ServiceResponse.fail(Consts.i18nKey.INVALID_RECRUITMENT_GROUP_CODE);
        }

        Map<String, Object> condition = map();
        condition.put("status", Recruitments.Status.NORMAL.name());
        condition.put("apply_end_time", new Date());
        condition.put("city", city);

        List<SimpleRecruitment4Worker> recruitments;
        // 如果是基于地理位置的分组，则直接从地理位置的条件进行筛选
        if (Recruitments.Groups.NEARBY.name().equalsIgnoreCase(groupCode)) {
            recruitments = find4UserByConditionBasedOnUserLocation(userid, condition, page);
        }
        // 否则则加上特定的条件之后，再从基于地理位置的基础上进行筛选
        else {
            condition.put(group.getAttrName(), group.getAttrValue());
            recruitments = find4UserByConditionBasedOnUserLocation(userid, condition, page);
        }
        return ServiceResponse.success().with("recruitments", recruitments);
    }

    /**
     * 不加任何其他条件，根据用户自身的特性列出所有符合用户特性的信息.
     * 和 group=NEARBY是一样的
     *
     * @param user
     * @return
     */
    @Override
    public ServiceResponse find4UserWithoutCondition(long user, String city, int page) {
        Map<String, Object> condition = map();
        condition.put("status", Recruitments.Status.NORMAL.name());
        condition.put("apply_end_time", new Date());
        condition.put("city", city);
        List<SimpleRecruitment4Worker> list = find4UserByConditionBasedOnUserLocation(user, condition, page);
        return ServiceResponse.success().with("recruitments", list);
    }

    /**
     * 根据地理位置条件进行工作筛选
     *
     * @param userid
     * @return
     */
    private List<SimpleRecruitment4Worker> find4UserByConditionBasedOnUserLocation(
            long userid, Map<String, Object> condition, int page) {
        UserLocation location = locationDao.get(userid);
        Double userLng = location.getLongitude();
        Double userLat = location.getLatitude();

        userLng = userLng == null ? 0d : userLng;
        userLat = userLat == null ? 0d : userLat;

        List<Map<String, Object>> maps = recruitmentDao.findByConditionAndUserGeo(condition, userLng, userLat, page);
        if (maps == null) {
            return null;
        }

        List<SimpleRecruitment4Worker> result = new LinkedList<>();
        for (Map<String, Object> map : maps) {
            SimpleRecruitment4Worker recruitment = wrap(map);
            if (recruitment != null) {
                result.add(recruitment);
            }
        }
        return result;
    }


    private ServiceResponse updateRecruitment(Recruitment recruitment, WorkLocation workLocation, ApplyCondition condition) {
        return null;
    }

    private List<Applier> getAppliers(Long recruitmentId) {
        List<Applier> appliers = new ArrayList<>();
        List<Offer> offers = offerDao.queryByRecruitmentId(recruitmentId);
        if (offers != null && offers.size() > 0) {
            for (Offer offer : offers) {
                long applierId = offer.getApplier();

                Applier applier = new Applier();
                applier.setRecruitmentId(recruitmentId);
                applier.setId(applierId);
                applier.setApplyTime(offer.getApplyTime());
                applier.setWorkStatus(offer.getStatus());
                applier.setPayResult(offer.getPayResult());
                applier.setRateResult(offer.getRateResult());

                Account account = accountDao.get(applierId);

                String avatar = account.getAvatar();
                if (StringUtils.isBlank(avatar)) {
                    avatar = Consts.DEFAULT_AVATAR;
                }
                applier.setAvatar(avatar);
                applier.setMobile(account.getUsername());

                UserProfile profile = userProfileDao.get(applierId);

                String nick = account.getNick();
                String gender = profile.getGender();
                String name = profile.getName();

                Nulls.fixWithDefault(nick, gender, name);

                applier.setNick(nick);
                applier.setGender(gender);
                applier.setName(name);

                UserDataProfile dataProfile = dataProfileDao.get(applierId);

                applier.setReceivedEggs(dataProfile.getReceivedEggs());
                applier.setReceivedFlowers(dataProfile.getReceivedFlowers());
                applier.setWorkDoneTimes(dataProfile.getWorkDoneTimes());

                appliers.add(applier);
            }
        }
        return appliers;
    }


    private Recruitment4Employer wrapRecruitment(Recruitment recruitment) {
        Long id = recruitment.getId();

        Recruitment4Employer wrapper = new Recruitment4Employer();
        wrapper.setRecruitment(recruitment);

        WorkLocation location = workLocationDao.getByRecruitmentId(id);
        wrapper.setWorkLocation(location);

        ApplyCondition condition = applyConditionDao.getByRecruitmentId(id);
        wrapper.setApplyCondition(condition);

        List<Applier> appliers = getAppliers(id);
        wrapper.setAppliers(appliers);
        return wrapper;
    }

    private ServiceResponse integrityCheck(RecruitmentType type, Recruitment recruitment, WorkLocation workLocation) {
        // check code is valid
        if (type == null) {
            LOGGER.error("Unknown recruitment type");
            return ServiceResponse.fail(Consts.i18nKey.UNKNOWN_RECRUITMENT_TYPE);
        }

        // check start/end date is valid
        Date workEndDate = recruitment.getWorkEndDate();
        Date workStartDate = recruitment.getWorkStartDate();

        if (workStartDate == null || workEndDate == null) {
            LOGGER.error("recruitment start date and end date are required");
            return ServiceResponse.fail(Consts.i18nKey.RECRUITMENT_START_AND_END_DATE_ARE_REQUIRED);
        }
        if (workEndDate.before(workStartDate)) {
            LOGGER.error("recruitment end date should after start date");
            return ServiceResponse.fail(Consts.i18nKey.RECRUITMENT_END_DATE_SHOULD_BEFORE_START_DATE);
        }

        // check work location is valid
        if (StringUtils.isBlank(workLocation.getAddress())) {
            //TODO: get user profile's address
            return ServiceResponse.fail(Consts.i18nKey.RECRUITMENT_WORK_LOCATION_IS_REQUIRED);
        }
        return ServiceResponse.success();
    }


    private void fixOptionalProperty(Recruitment recruitment, ApplyCondition condition) {
        // 设置默认的上班时间
        if (StringUtils.isBlank(recruitment.getOnDutyTime())) {
            recruitment.setOnDutyTime(Consts.DEFAULT_ONDUTY_TIME);
        }
        // 设置默认的工作时长
        Integer workingHours = recruitment.getWorkingHours();
        if (workingHours == null || workingHours.intValue() <= 0) {
            recruitment.setWorkingHours(Consts.DEFAULT_WORKING_HOURS);
        }
        // 设置默认的 自动录取
        if (recruitment.getAutoRecruit() == 0) {
            recruitment.setAutoRecruit(YesNo.Yes.charVal);
        }
        // 如果没有设置招聘截至日期，则默认为工作开始日期的前一天
        if (recruitment.getApplyEndTime() == null) {
            recruitment.setApplyEndTime(DateUtils.addDays(recruitment.getWorkStartDate(), -1));
        }
        // 设置申请条件默认值
        if (StringUtils.isBlank(condition.getGender())) {
            condition.setGender(ApplyConditions.UNLIMITED.name());
        }
        if (condition.getHeight() == null) {
            condition.setHeight(Consts.DEFAULT_HEIGHT_REQUIREMENT);
        }
        if (condition.getMaxAge() == null) {
            condition.setMaxAge(Consts.DEFAULT_MAX_AGE_REQUIREMENT);
        }
        if (condition.getMinAge() == null) {
            condition.setMinAge(Consts.DEFAULT_MIN_AGE_CONDITION);
        }
        Character workExperience = condition.getWorkExperience();
        if (workExperience == null) {
            condition.setWorkExperience(YesNo.No.charVal);
        }
    }

    private SimpleRecruitment4Worker wrap(Map<String, Object> result) {
        Map<String, Object> map = RowDataUtil.toCamelCase(result);
        try {
            SimpleRecruitment4Worker recruitment = new SimpleRecruitment4Worker();
            ApplyCondition condition = new ApplyCondition();
            BeanUtils.populate(condition, map);

            WorkLocationWithDistance workLocation = new WorkLocationWithDistance();
            BeanUtils.populate(workLocation, map);

            Recruitment rec = new Recruitment();
            BeanUtils.populate(rec, map);

            recruitment.setRecruitment(rec);
            recruitment.setApplyCondition(condition);
            recruitment.setWorkLocation(workLocation);

            return recruitment;
        } catch (Exception e) {
            LOGGER.error("Error while get recruitment", e);
            return null;
        }
    }


    private ServiceResponse createNewRecruitment(Recruitment recruitment, WorkLocation workLocation, ApplyCondition condition) {
        String code = recruitment.getTypeCode();
        RecruitmentType type = recruitmentTypeDao.findByCode(code);

        ServiceResponse response = integrityCheck(type, recruitment, workLocation);
        if (response.isFailed()) {
            return response;
        }

        // 补全选填的属性
        fixOptionalProperty(recruitment, condition);

        recruitment.setStatus(Recruitments.Status.NORMAL.name());

        // 计算平均时薪
        int hourlyPay = getHourlyPay(recruitment);
        recruitment.setHourlyPay(hourlyPay);

        // 获取招聘信息所属行业
        long owner = recruitment.getOwner();
        Company company = companyDao.findByOwner(owner);
        String tradeCategory = company.getTradeCategory();
        recruitment.setTradeCategory(tradeCategory == null ? "" : tradeCategory);

        // 保存招聘信息
        recruitmentDao.insert(recruitment);
        Long id = recruitment.getId();
        // save work location
        //TODO: 客户端应该传『省』，『市』，『区』和经纬度进来
        workLocation.setRecruitmentId(id);
        String address = workLocation.getAddress();
        GeoCoordinate coordinate = baiduClient.coordinate(address);
        if (coordinate != null) {
            GeoCoordinate.Location location = coordinate.getResult().getLocation();
            workLocation.setLatitude(location.getLat());
            workLocation.setLongitude(location.getLng());
        } else {
            LOGGER.warn("Retrieve Coordinate from Baidu failed for address:{}", address);
        }
        workLocationDao.insert(workLocation);
        // save apply condition
        condition.setRecruitmentId(id);
        applyConditionDao.insert(condition);

        return ServiceResponse.success();
    }

    private int getHourlyPay(Recruitment recruitment) {
        Integer salary = recruitment.getSalary();
        String salaryUnit = recruitment.getSalaryUnit();
        if (SalaryUnit.HOUR.name().equalsIgnoreCase(salaryUnit)) {
            return salary;
        }

        Integer workingHours = recruitment.getWorkingHours();
        if (workingHours == null || workingHours == 0) {
            workingHours = Consts.DEFAULT_WORKING_HOURS;
        }

        if (SalaryUnit.DAY.name().equalsIgnoreCase(salaryUnit)) {
            return salary / workingHours;
        }
        if (SalaryUnit.WEEK.name().equalsIgnoreCase(salaryUnit)) {
            return salary / (workingHours * Consts.DEFAULT_WORKING_DAYS_A_WEEK);
        }

        if (SalaryUnit.MONTH.name().equalsIgnoreCase(salaryUnit)) {
            return salary / (workingHours * Consts.DEFAULT_WORKING_DAYS_A_MONTH);
        }
        return 0;
    }


    private Map<String, Object> newMapWithBasicCondition() {
        Map<String, Object> params = map();
        params.put("status", Recruitments.Status.NORMAL.name());
        params.put("apply_end_time", new Date());
        return params;
    }

    public void setRecruitmentTypeDao(RecruitmentTypeDao recruitmentTypeDao) {
        this.recruitmentTypeDao = recruitmentTypeDao;
    }

    public void setRecruitmentDao(RecruitmentDao recruitmentDao) {
        this.recruitmentDao = recruitmentDao;
    }

    public void setWorkLocationDao(WorkLocationDao workLocationDao) {
        this.workLocationDao = workLocationDao;
    }

    public void setApplyConditionDao(ApplyConditionDao applyConditionDao) {
        this.applyConditionDao = applyConditionDao;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setUserProfileDao(UserProfileDao userProfileDao) {
        this.userProfileDao = userProfileDao;
    }

    public void setDataProfileDao(UserDataProfileDao dataProfileDao) {
        this.dataProfileDao = dataProfileDao;
    }

    public void setOfferDao(OfferDao offerDao) {
        this.offerDao = offerDao;
    }

    public void setRecruitmentGroupDao(RecruitmentGroupDao recruitmentGroupDao) {
        this.recruitmentGroupDao = recruitmentGroupDao;
    }

    public void setCompanyDao(CompanyDao companyDao) {
        this.companyDao = companyDao;
    }

    public void setBaiduClient(BaiduClient baiduClient) {
        this.baiduClient = baiduClient;
    }

    public void setLocationDao(UserLocationDao locationDao) {
        this.locationDao = locationDao;
    }

}
